/*
*********************************************************************************************************
*
*
*                              Motorola S-records Support packetage
*                                        S-records 支持包
*
* File    : SRecord.c
* By      : Lin Shijun(http://blog.csdn.net/lin_strong)
* Date    : 2018/03/03
* Version : V1.0
* Note    :
*
*********************************************************************************************************
*/

#include "../devC++/SRecord.h"
#include <ctype.h>

/*
*********************************************************************************************************
*                                      LOCAL FUNCTION DECLARATION
*********************************************************************************************************
*/
// description: 将十六进制字符对转换为对应的字节。
// argument:  buf      pointer to the char waiting for convert.
//            perr     return the err
//                     0    if success
//                     1    if find not xdigit char
// return:    the converted value.
static INT8U GetHexByte(INT8U *buf, INT8U *perr);
// description: convert the byte to coresponding hex char pair.
// argument:  val      the byte to convert
//            buf      pointer to the buffer to hold the char pair, buffer should have
//                     two place at least.
// return:    pointer to the buf+2
static INT8U* PutHexCharPair(INT8U val, INT8U *buf);

/*
*********************************************************************************************************
*                                        SRecord_RecToStr()
*
* Description : 试着转换字符串为对应的S-record
*
* Arguments   : buf      pointer to the buffer that holds input.  指向要转换的字符串
*               result   return the result,if success;            返回成功转换后的S-record
*
* return      : SREC_ERR_NO         if success.
*               SREC_ERR_FORMAT     if error in format, e.g. first char is not 'S', char in DATA is
*                                      not digit.
*               SREC_ERR_TYPE       if type of record is invalid(i.e. S4 or S6)
*               SREC_ERR_CHECKSUM   if the checksum is wrong.
*               SREC_ERR_TOOSHORT   if the length of the record is too short(according to the LENGTH field)
*               SREC_ERR_TOOLONG    if the length of the record is too long(according to the LENGTH field)
*
* Note(s): 1. for the S0 record ,this function just simply mark the result->RecType as S0,
*               then return; with no further resolution.
*              对于S0记录，这个函数仅仅直接标记result->RecType为S0，然后就返回了，不做进一步解析。
*          2. there is no need for string to be ended with '\0', for the length of the S-record is
*               given in the LENGTH field, and thanks to the CKECKSUM, if anything is wrong, it's
*               probobly been found.
*             实际上输入字符串没必要用'\0'来结尾，因为是根据S-record的LENGTH字段得知记录长度的，如果出了
*             任何问题，几乎都能通过校验码来发现。(除非正好让校验码通过了，这概率太小忽略不计)
*********************************************************************************************************
*/
INT8U SRecord_StrToRec(INT8U *buf, SRECORD_STRUCT *result) {
	INT8U c, checksum, err;
	INT8U i, bytesLeftCnt, addrBytesCnt;
	INT8U *rstBuf;
	INT32U address;

	// TYPE field
	if (*(buf++) != 'S')
		return SREC_ERR_FORMAT;
	c = *(buf++) - '0';
	if (c > 9)
		return SREC_ERR_FORMAT;
	if (c == 4 || c == 6)
		return SREC_ERR_TYPE;
	result->RecType = c;
	if (c == SRECORD_TYPE_S0)
		return SREC_ERR_NO;

	// LENGTH field
	bytesLeftCnt = GetHexByte(buf, &err);
	if (err != 0)
		return SREC_ERR_FORMAT;
	buf += 2;

	checksum = bytesLeftCnt;

	// here, c == TYPE, addrBytesCnt == length of ADDRESS
	if (c == SRECORD_TYPE_S1 || c == SRECORD_TYPE_S5 || c == SRECORD_TYPE_S9) {
		addrBytesCnt = 2;
	} else if (c == SRECORD_TYPE_S2 || c == SRECORD_TYPE_S8) {
		addrBytesCnt = 3;
	} else {  //  if (c == SREC_TYPE_S3 || c == SREC_TYPE_S7)
		addrBytesCnt = 4;
	}

	if (bytesLeftCnt <= addrBytesCnt)  // at least a CHECKSUM field
		return SREC_ERR_TOOSHORT;
	bytesLeftCnt -= addrBytesCnt;

	// only S1,S2,S3 use Data field
	if (c > SRECORD_TYPE_S3) {
		// only a CHECKSUM field
		if (bytesLeftCnt > 1)
			return SREC_ERR_TOOLONG;
	} else {
		// only S1,S2,S3 use Data field
		if (bytesLeftCnt > SRECORD_DATA_MAXLENGTH + 1)
			return SREC_ERR_TOOLONG;
	}

	address = 0;
	// ADDRESS field
	for (i = 0; i < addrBytesCnt; i++) {
		c = GetHexByte(buf, &err);
		if (err != 0)
			return SREC_ERR_FORMAT;
		buf += 2;
		address = (address << 8) + c;
		checksum += c;
	}
	result->LoadAddr = address;

	result->DataLen = bytesLeftCnt - 1;
	rstBuf = result->Data;
	// DATA field
	for (i = 0; i < bytesLeftCnt - 1; i++) {
		c = GetHexByte(buf, &err);
		if (err != 0)
			return SREC_ERR_FORMAT;
		buf += 2;
		*(rstBuf++) = c;
		checksum += c;
	}

	// CHECKSUM field
	c = ~GetHexByte(buf, &err);
	if (err != 0)
		return SREC_ERR_FORMAT;
	if (checksum != c)
		return SREC_ERR_CHECKSUM;//*/
	return SREC_ERR_NO;
}
/*
*********************************************************************************************************
*                                        SRecord_RecToStr()
*
* Description : 将 S 记录转换为字符串（以 '\0' 结尾）。
*
* Arguments   : srec           the S-record to be converted;             要转换的S-record
*               result         pointer to the buffer that holds result.  指向存放结果的缓冲区
*
* return      : SREC_ERR_NO         if success.
*               SREC_ERR_TYPE       if type of record is invalid(i.e. S0,S4 or S6 and > 9)
*               SREC_ERR_TOOLONG    if the length of the record is too long(according to the LENGTH field)
*
* Note(s): 1. this function don't support S0, though it's defined in the standard.
*              虽然标准里有S0，但这个函数不支持转换它
*          2. user should make sure that the result buffer is big enough.
*              用户需要自己确保保存结果的缓冲区足够大
*********************************************************************************************************
*/
INT8U SRecord_RecToStr(SRECORD_STRUCT *srec, INT8U *result) {
	INT8U c, checksum = 0;
	INT8U len, i, addrBytesCnt;
	c = srec->RecType;
	// 不支持转换S0
	if ( c > 9 || c == 4 || c == 6 || c == SRECORD_TYPE_S0)
		return SREC_ERR_TYPE;

	// TYPE field
	*result++ = 'S';
	*result++ = c + '0';

	if (c == SRECORD_TYPE_S1 || c == SRECORD_TYPE_S5 || c == SRECORD_TYPE_S9) {
		addrBytesCnt = 2;
	} else if (c == SRECORD_TYPE_S2 || c == SRECORD_TYPE_S8) {
		addrBytesCnt = 3;
	} else {  //  if (c == SREC_TYPE_S3 || c == SREC_TYPE_S7)
		addrBytesCnt = 4;
	}
	if (srec->DataLen > SRECORD_DATA_MAXLENGTH)
		return SREC_ERR_TOOLONG;

	// 只有S1,S2,S3使用数据字段
	// LENGTH field
	if (c == SRECORD_TYPE_S1 || c == SRECORD_TYPE_S2 || c == SRECORD_TYPE_S3)
		len = srec->DataLen + addrBytesCnt + 1;   // ADDRESS + DATA + CHECKSUM
	else
		len = addrBytesCnt + 1;                   // ADDRESS + CHECKSUM
	result = PutHexCharPair(len, result);
	checksum += len;

	// ADDRESS field
	for (i = addrBytesCnt; i > 0 ; i--) {
		c = (INT8U)(srec->LoadAddr >> ((i - 1) * 8));
		result = PutHexCharPair(c, result);
		checksum += c;
	}
	len -= addrBytesCnt;

	// DATA field
	for (i = 0; i < len - 1; i++) {           // the least is the check sum
		c = srec->Data[i];
		result = PutHexCharPair(c, result);
		checksum += c;
	}

	// CHECKSUM field
	result = PutHexCharPair(~checksum, result);

	*result = '\0';
	return SREC_ERR_NO;
}


/*
*********************************************************************************************************
*                                      LOCAL FUNCTION DEFINITION
*********************************************************************************************************
*/

// description: 将十六进制字符对转换为对应的字节。
// argument:  buf      pointer to the char waiting for convert.
//            perr     return the err
//                     0    if success
//                     1    if find not xdigit char
// return:    the converted value.
static INT8U GetHexByte(INT8U *buf, INT8U *perr) {
	INT8U c, rst;

	*perr = 0;
	c = *(buf++);       //get an ASCII hex byte
	if (!isxdigit(c)) { //is it a valid hex digit
		*perr = 1;        //no. return an error
		return 0;
	}

	//convert the ASCII character
	rst = isdigit(c) ? (c - '0') : (c - 'A' + 10);

	c = *buf;           //get next ASCII hex byte
	if (!isxdigit(c)) { //is it a valid hex digit
		*perr = 1;        //no. return an error
		return rst;
	}

	//convert the ASCII character
	rst = (isdigit(c) ? (c - '0') : (c - 'A' + 10)) + (rst << 4);

	return (rst);        //return 'no error'
}

static INT8U* PutHexCharPair(INT8U val, INT8U *buf) {
	INT8U c;
	c = val >> 4;
	*buf++ = (c > 9) ? (c - 10 + 'A') : (c + '0') ;
	c = val & 0x0F;
	*buf++ = (c > 9) ? (c - 10 + 'A') : (c + '0') ;
	return buf;
}
